﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace ColorLabelForm
{
    /**
     For example: A <bold>quick</bold> <red>brown</red> fox </blue>jump</blue> over the <italic>lazy dog</italic>
    **/
    public class ColorLabel : Label
    {
        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        
        private const string Pattern = @"<(\w+)>(.*?)</(\w+)>";
        
        private readonly List<FormatCluster> _list = new List<FormatCluster>();
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new bool AutoSize { get { return false; } }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new bool AutoEllipsis { get { return false; } }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new Image Image { get {return null; } }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new int ImageIndex { get { return -1; } }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new string ImageKey { get{return null;}  }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new ContentAlignment ImageAlign { get { return ContentAlignment.MiddleCenter; } }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new bool UseMnemonic { get {return true;} }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new bool UseCompatibleTextRendering { get { return false; } }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new FlatStyle FlatStyle { get {return FlatStyle.Standard;} }
        
        public ColorLabel()
        {
            InitializeComponent();
        }
        
        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
        
        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }
        
        private static Rectangle DeflateRect(Rectangle rect, Padding padding)
        {
            rect.X += padding.Left;
            rect.Y += padding.Top;
            rect.Width -= padding.Horizontal;
            rect.Height -= padding.Vertical;
            return rect;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            Rectangle face = DeflateRect(ClientRectangle, Padding);

            const TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.WordBreak;

            int pos = 0;
            foreach (FormatCluster cluster in _list)
            {
                Font f = null;
                Color color;
                switch (cluster.Type)
                {
                    case "bold":
                        f = new Font(this.Font, FontStyle.Bold);
                        color = ForeColor;
                        break;
                    case "italic":
                        f = new Font(this.Font, FontStyle.Italic);
                        color = ForeColor;
                        break;
                    case "red":
                        f = new Font(this.Font, FontStyle.Regular);
                        color = System.Drawing.Color.Red;
                        break;
                    case "blue":
                        f = new Font(this.Font, FontStyle.Regular);
                        color = System.Drawing.Color.BlueViolet;
                        break;
                    case "text":
                    default:
                        f = new Font(this.Font, FontStyle.Regular);
                        color = ForeColor;
                        break;
                }
                foreach (var element in cluster.Elements)
                {
                    if (pos + element.Width > face.Width || element.Text.Contains(Environment.NewLine))
                    {
                        pos = 0;
                        face.Y += f.Height;
                        face.Height -= f.Height;
                    }
                    if (element.Width == 0)
                        continue;
                    TextRenderer.DrawText(e.Graphics, element.Text, f, new Rectangle(face.X + pos, face.Y, face.Width - pos, face.Height), color, flags);
                    pos += element.Width;
                }
                f.Dispose();
            }
        }

        protected override void OnFontChanged(EventArgs e)
        {
            _list.ForEach((cluster) => cluster.CalcWidths(Font));
            Invalidate();
        }

        [EditorAttribute("System.ComponentModel.Design.MultilineStringEditor, System.Design", "System.Drawing.Design.UITypeEditor")]
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                Parse(value);
                base.Text = value;
            }
        }
        
        private void Parse(string value)
        {
            int ready = 0;
            Regex re = new Regex(Pattern, RegexOptions.Singleline);
            var matches = re.Matches(value);
            _list.Clear();
            foreach (Match m in matches)
            {
                System.Console.WriteLine(m.Groups[1].Value);
                int position = m.Index;
                int length = m.Value.Length;
                if (position > ready)
                {
                    FormatCluster cluster = new FormatCluster
                    {
                        Type = "text",
                        Text = value.Substring(ready, position - ready)
                    };
                    cluster.CalcWidths(this.Font);
                    _list.Add(cluster);
                }
                {
                    FormatCluster cluster = new FormatCluster {Type = m.Groups[1].Value, Text = m.Groups[2].Value};
                    cluster.CalcWidths(this.Font);
                    _list.Add(cluster);
                }
                ready = position + length;

            }
            if (ready < value.Length)
            {
                FormatCluster cluster = new FormatCluster
                {
                    Type = "text",
                    Text = value.Substring(ready, value.Length - ready)
                };
                cluster.CalcWidths(this.Font);
                _list.Add(cluster);
            }
        }
        private class FormatCluster
        {
            private readonly List<Element> _elements = new List<Element>();

            public string Type { get; set; }

            public string Text { get; set; }

            // it is difficult to calculate the accurate width of text
            // for surrogate pairs, it is only possible to get the approximate value
            public List<Element> Elements { get { return _elements; } }

            public void CalcWidths(Font f)
            {
                IntPtr hWnd = IntPtr.Zero;
                IntPtr hdc = GetDC(hWnd);
                Font font = null;
                _elements.Clear();
                if (Type == "bold")
                {
                    font = new Font(f, FontStyle.Bold);
                }
                else if (Type == "italic")
                {
                    font = new Font(f, FontStyle.Italic);
                }
                else
                {
                    font = new Font(f, FontStyle.Regular);
                }
                IntPtr hGDIObj = font.ToHfont();
                IntPtr handle = SelectObject(hdc, hGDIObj);
                TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(Text);
                Element el = null;
                while (charEnum.MoveNext())
                {
                    string textElement = charEnum.GetTextElement();
                    //int unicode = textElement.Length == 2 ? Char.ConvertToUtf32(textElement[0], textElement[1]) : Char.ConvertToUtf32(textElement, 0);
                    int unicode = textElement.Length == 2 ? (textElement[0] << 16 | textElement[1]) : textElement[0];
                    int width = 0;
                    if (unicode < 32 && unicode > 0)
                    {
                        // non-printable character
                        if (el == null || el.Width > 0)
                        {
                            el = new Element(textElement, width);
                            _elements.Add(el);
                        }
                        else
                        {
                            el.Text += textElement;                            
                        }
                    }
                    else if (textElement.Length == 1)
                    {
                        ABC[] glyphSet = new ABC[1];
                        GetCharABCWidthsW(hdc, (uint)unicode, (uint)unicode, glyphSet);
                        width = glyphSet[0].abcA + (int)glyphSet[0].abcB + glyphSet[0].abcC;
                        // space and non-ascii character should be saved alone
                        if (unicode == 32 || unicode > 127)
                        {
                            el = new Element(textElement, width);
                            _elements.Add(el);
                            el = null;
                        }
                        else
                        {
                            if (el == null)
                            {
                                el = new Element(textElement, width);
                                _elements.Add(el);
                            }
                            else
                            {
                                el.Width += width;
                                el.Text += textElement;
                            }
                        }

                    }
                    else
                    {
                        var bmp = new Bitmap(100, 100);
                        var g = Graphics.FromImage(bmp);
                        width = TextWidth(g, font, textElement);
                        el = new Element(textElement, width);
                        _elements.Add(el);
                        el = null;
                        g.Dispose();
                        bmp.Dispose();
                    }
                }
                SelectObject(hdc, handle);
                DeleteObject(hGDIObj);
                ReleaseDC(hWnd, hdc);
                font.Dispose();
            }

            private int TextWidth(Graphics g, Font font, string txt)
            {
                StringFormat format = new StringFormat(StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.NoWrap);
                RectangleF rect = new RectangleF(0, 0, 1000, 1000);
                CharacterRange[] ranges = { new CharacterRange(0, txt.Length) };
                Region[] regions = new Region[1];

                format.SetMeasurableCharacterRanges(ranges);

                regions = g.MeasureCharacterRanges(txt, font, rect, format);
                var w1 = regions[0].GetBounds(g).Width;
                string ruler = new string(txt[txt.Length - 1], txt.Length);
                regions = g.MeasureCharacterRanges(ruler, font, rect, format);
                var w2 = regions[0].GetBounds(g).Width;
                if (w1 > w2)
                {
                    return (int)w1 + 1;
                }
                return (int)w1;
            }
        }

        private class Element
        {
            public string Text { get; set; }
            public int Width { get; set; }

            public Element(string txt, int width)
            {
                Width = width;
                Text = txt ?? String.Empty;
            }
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct ABC
        {
            public int abcA;
            public uint abcB;
            public int abcC;
        }

        [DllImport("gdi32.dll")]
        private static extern int GetCharABCWidthsW(IntPtr hdc, uint start, uint end, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStruct)] ABC[] lpABC);

        [DllImport("gdi32.dll")]
        private static extern int DeleteObject(IntPtr hGdiObj);

        [DllImport("user32.dll")]
        private static extern IntPtr GetDC(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

        [DllImport("gdi32.dll")]
        private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hGdiObj);
    }
}
